During execution of a computer program, there may be errors or other conditions (referred to generally as “exceptions”) which prevent the function (e.g., method or subroutine) that is currently executing from continuing along its normal execution path. Since the function cannot handle the error, the function communicates the error to its calling function. A simple approach of communicating the error is for the function to return an error code. This approach, however, has a couple of disadvantages. First, it requires the signature of the function to include a parameter for returning the error code. Second, it requires the calling function to parse and respond to the error. If the calling function cannot handle the error, the calling function needs to communicate the error to its calling function. Such communicating of the error up the chain of calling functions requires the signature of each calling function to include a parameter for the error code and requires the calling function to handle the returned error code. This is known as the “semipredicate problem.” The expanded signatures and added code can obscure the essential behavior of the functions. In addition, the necessity of altering the signature of a large number of functions due to the addition of explicit error handling creates a significant maintenance problem. Moreover, error-checking code is often added late in the development phase of a computer program which tends to increase the cost of this approach. Along with the signature change, this approach devotes a significant portion of the code to error handling.
Structured exception handling (“SEH”) was introduced to help address the semipredicate problem by separating error-handling code from code essential to the behavior of a function. Many programming languages provide a SEH construct, consisting of a try-clause, any number of catch-clauses, and an optional finally-clause. Usually, at least one catch-clause or the finally-clause is present. The SEH construct defines an exception scope (“scope”) in that when an exception occurs within that SEH construct, it is handled within that exception scope.
When executing statements within the try-clause of a SEH construct if an exception occurs, then control is transferred immediately to the start of a matching catch-clause within that SEH construct if there is one. If there is a matching catch-clause, the exception is considered “handled” within the exception scope of that SEH construct. Whether or not there is an exception and whether or not a catch-clause executes, the finally-clause if present executes before control exits the SEH construct. If the exception is not handled in the immediately enclosing SEH construct, SEH constructs in enclosing exception scopes are searched for a matching catch-clause. This process continues until a match is found or until there are no more enclosing SEH constructs.
An exception is created explicitly in the code, usually through the execution of a throw-statement. The throw-statement indicates that the program has determined that normal execution cannot proceed. Typically, this occurs when an inconsistency has been encountered and cannot be resolved by the function given the information available to it. The throw-statement indicates the programmer's intention to abort the execution of the currently active try-clause and to resume execution in the most deeply-nested catch-clause that matches that exception. An exception matches a try-clause when the types of the arguments of the exception and the catch-clause are the same.
Within the program code, a SEH construct can be characterized as having a static exception scope. The catch-clauses and the finally-clause within that SEH construct are associated with that exception scope. During execution of that program code, when control enters a SEH construct, a dynamic exception scope is created having the characteristics associated with the static exception scope. The associated catch-clauses are enabled while that exception scope is active. When control reaches the end of that exception scope, the associated catch-clauses are no longer enabled. The exception scopes associated with SEH constructs can be nested, both statically and dynamically.
The process of determining which catch-clause to execute in response to the creation of a given exception involves searching the hierarchy of dynamic exception scopes. In the most deeply nested construct, execution of a throw-statement terminates normal program flow in the enclosing try-block. Control structures are exited and resources reclaimed as specified by the language—typically as if a break-statement had been encountered at every level out to the end of the try-clause. Then the set of catch-clauses associated with that SEH construct are searched for one which matches the exception—using rules specified by the language. If a match is found, then the body of that catch-clause is executed, then the finally-clause is executed. Following that, the exception scope associated with the SEH construct is exited and execution proceeds normally from that point. If no matching catch-clause is found, the finally-clause is still executed. Following that, the exception scope associated with the SEH construct is exited. However, the exception remains active as if it had been thrown immediately after the end of the SEH construct. This causes the next most-deeply nested exception scope associated with a SEH construct to be searched for a matching catch-clause. This process continues until either a matching catch-clause is found or control exits the exception scope in which the executing thread or process was created. The particular programming language typically defines what happens when a thread or process terminates when an exception is still active.
The run-time mechanisms to support structured exception handling use a dynamic registration model or a table-driven model. With the dynamic registration model, exception handlers (“handlers”) that are currently active are updated as the exception scopes associated with SEH constructs are entered and exited. With the table-driven model, tables are created that map the ranges of values assumed by the program counter to handlers that are active in that static exception scope. The advantages of the dynamic registration model include a straightforward implementation and separation of implementation from the language. An advantage of the table-driven model is no run-time overhead. However, it can be applied only if the exception-handling constructs are statically scoped. The table-driven model can be viewed as an optimization that is applicable when the associated exception-handling constructs are statically defined. Another advantage of the table-driven model is that it is inherently thread-safe because the program text is assumed to be constant and the underlying execution model is assumed to be thread-safe. In particular, the hardware, kernel software, and language execution model guarantee that the instruction pointer queried for a specific thread will be accurate. Thus, the table-driven model selects the correct set of handlers at any point during the execution of that thread.
With the dynamic registration model, a compiler partitions a SEH construct into a “normal” and an “exceptional” component. The normal component is executed in-line with the normal control flow. The exceptional component is packaged as an exception handler function. The compiler adds code to the normal component to install the exception handler function at a point corresponding to entry to the try-block and to uninstall the handler at a point corresponding to the start of the finally-block.
The mechanism for implementing exceptions can be represented by a Scope object which is created when a SEH construct is entered and destroyed when that block is exited. This mechanism can be illustrated by the pseudo code in C++ syntax of Tables 1-5. The Scope class is illustrated in Table 1.
TABLE 1class Scope{ public:  typedef void (*handler_t)(Scope&, object&); private:  handler_t handler; public:  Scope(handler_t hdlr) : handler(hdlr) { } void handle(object& o) {  (*handler)(this, o); }};The example code of Table 2 can be used to illustrate the mechanism of the Scope class.
TABLE 2try stmt_try;catch (T1& t1) stmt_c1;catch (T2& t2) stmt_c2;catch ( ... ) stmt_any;finally stmt_fin;For this example code, a compiler would generate an “exceptional” component as illustrated in Table 3.
TABLE 3void handler(Scope& scope, object& o){ if (can_dispatch<T1>(o))  stmt_c1; else if (can_dispatch <T2 > (o))  stmt_c2; else  stmt_any; goto resume;}The compiler would generate a “normal” component as illustrated in Table 4.
TABLE 4try: Scope* scope = new Scope(handler); stmt_try;resume: stmt_fin; delete scope;The compiler would generate the code for the throw-stmt with the argument expr as illustrated by Table 5.
TABLE 5scope->handle(expr);
As shown in Table 4, this mechanism would result in the scope object of the Scope class being instantiated when the try-clause is entered. The constructor of the scope object is passed a reference to the handler function of the exceptional component of Table 3. The try-statement would then be executed. As shown in Table 5, to throw an exception, the try-statement would invoke the handle method of the scope object passing an exception argument indicating the type of the exception. The handle method would then invoke the handler function (passed when the scope object was instantiated) passing a reference to the scope object and the exception argument. As illustrated in Table 3, the handler function determines whether the exception matches any of the catch-clauses. Since the example code includes a catch-clause with an ellipsis, the last catch-clause will match any exception. The handler function executes the catch-statement of the matching catch-clause. The handler function then continues execution at the resume label of Table 4 to execute the finally-statement and destroy the scope object. (Note: The “goto” statement from within a function to outside the function may not be supported by the programming language but is used to illustrate where execution continues irrespective of the mechanism actually employed.)
The example codes of Tables 1-5 do not address the situation when the exception does not match a catch-clause. FIG. 1 illustrates example data structures for tracking exception scopes for use in locating matching catch-clauses. The data structures 100 for each thread include a machine state 110, an execution stack 120, exception scope stacks 130, and handlers 140. The machine state stores the current state of the thread and includes an instruction pointer (“IP”) 111 and a frame pointer (“FP”) 112. The frame pointer points to the top activation frame 123 of the execution stack for the thread. When a function is called during execution of the thread, a new activation frame is added to the execution stack. The activation frames stores the variables defined to be within the scope of the called function. When a function returns to its calling function, the activation frame of the function is removed from the top of the execution stack and the frame pointer is adjusted to point to the activation frame of the calling function. For example, when the function associated with activation frame 123 returns, activation frame 123 is removed from the execution stack and the frame pointer is set to point to activation frame 122. The instruction pointer would also be adjusted to point to the instruction in the calling function after the instruction that resulted in the call to the function.
Each activation frame points to one of the exception scope stacks 130. Each exception scope stack includes an exception scope (or just “scope”) for each active SEH construct within the function. For example, activation frame 123 points to exception scope stack 130C, which contains scopes 137, 138, and 139 for the three active scopes of the function. When a SEH construct is entered, a new scope is added to the top of the exception scope stack; and when a SEH construct is exited, the scope is removed from the top of the exception scope stack. For example, if scope 138 was currently at the top of the exception stack and a SEH construct was entered, then scope 139 is added to the top of the exception scope stack 130C. When an exception occurs, the scope at the top of the exception scope stack is identified and its handler is invoked. If the handler handles the exception, the scope at the top of the stack is removed and execution continues at the finally-block, if any. If not handled, the scope is also removed from the top of the stack and the process is repeated for the next scope on the exception scope stack for the function. When the last scope of an exception scope stack is removed without finding a handler that handles the exception, then the activation frame at the top of the execution stack is removed and the function effectively returns to the calling function by checking to see if the exception can be handled by any handlers of the calling function. If the exception is not handled by any handers of any of the activation frames, then the final disposition of the exception is defined by the language.
With some languages, a terminate function is called when the exception is not handled. The terminate function would normally terminate the thread. However, a terminate handler can be installed on a thread-by-thread basis to effectively allow the program to decide how to handle the exception. With other languages, an uncaught exception handler for the thread is called. If the thread does not have an uncaught exception handler, then an uncaught exception method of a parent thread is invoked. These languages confine exception handling to a given thread or its ancestor threads.